﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using log4net;

namespace CSharpExt.Util
{
    /*
     * L'utilizzo di un pool di oggetti e' utile ogni qualvolta e' possibile
     * identificare un contesto in cui una certa tipologia di oggetti viene 
     * istanziata, utilizzata per un breve tempo, quindi rilasciata.
     * 
     * Creare in continuazione tali oggetti, e delegare il GC il compito della 
     * loro rimizione, comporta una perdita di tempo inutile:
     * 
     * - tempo per allocare la memoria, ed chiamare il costruttore per ogni istanza
     * - tempo per chiamare il distruttore (se esiste) e rilasciare la memoria
     * 
     * Ora, se il contesto di utilizzo di tali oggetti e' bend delimitato, 
     * puo' essere piu' efficiente utilizzare un 'pool' a cui richiedere tali oggetti
     * ed a cui rilasciare tali oggetti quando non sono piu' necessari.
     * 
     * In questo modo, si evita, o comunque si riduce notevolmente, i tempi 
     * utilizzati per la creazione e la rimozione di tali oggetti.
     * 
     * Si possono immaginare due tipi di pool:
     * 
     * - pool illimitati, in cui se un oggetto non e' gia' presente nel pool
     *   viene automaticamente creato
     * - pool limitati, in cui e' possibile acquisire solo un certo numero di oggetti
     * 
     * Entrambi i pool hanno pregi e diffetti:
     * 
     * - il pool illimitato, se non viene mai vuotato, potrebbe contenere un numero
     *   di oggetti pari al massimo richiesto, ma mai piu' utilizzati
     * - il pool limitato, nei momenti di carico, potrebbe non essere in grado di 
     *   soddisfare le richieste.
     *   
     * La seguente libreria vuole cercare di soddisfare entrambe le necessita':
     * 
     * - il template 'Pool<T>' permette di creare dei pool illimitati
     * - il template 'FixedPool<T>' permette di creare dei pool limitati
     * 
     * Entrambi i pool mettodo a disposizione i seguenti metodi:
     * 
     * - Acquire(): permette di richiedere un nuovo oggetto.
     *              Nel caso di 'Pool', se il pool e' vuoto, viene automaticamente
     *              creato, nel caso di 'FixedPool', ci si mette in attesa che un
     *              oggetto si liberi
     * - Release(T): rilascia un oggetto nel pool
     * 
     * L'implementazione di default prevede la possibilita' di vuotare tali pool
     * ogni 60 secondi. E' possibile disabilitare tale operazione, impostando
     * il timeout a 0.
     * 
     * Per entrambi e' possibile impostare un numero massimo di oggetti:
     * - nel caso di 'Pool', l'implementazione di default semplicemente esegue un log
     * - nel caso di 'FixedPool', ci si mette in attesa 
     * 
     * Se T e' un tipo che implementa IDisposable, quando il pool viene vuotato, 
     * viene chiamato il metodo Dispose di ogni oggetto ivi presente.
     */

    /// <summary>
    /// Classe base per i pool illimitati
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Pool<T> : IDisposable where T : class, new()
    {
        #region Private Data

        protected ILog _logger;

        /// <summary>
        /// Nome del pool.
        /// </summary>
        protected String _name = typeof(T).Name;

        /// <summary>
        /// Pool di oggetti
        /// </summary>
        protected Queue<T> _pool = new Queue<T>();

        /// <summary>
        /// Numero massimo di elementi acquisibili.
        /// Oltre a questo numero ci sono diverse possibilita':
        /// 
        /// - viene segnalato con un WARN
        /// - viene generata un'eccezione
        /// - si mette in attesa
        /// 
        /// </summary>
        protected int _size;

        /// <summary>
        /// Numero di oggetti correntemente in uso
        /// </summary>
        protected int _inUse;

        /// <summary>
        /// Ogni quanto tempo il pool deve essere cancellato
        /// </summary>
        protected int _clearTimeout = 60000;

        /// <summary>
        /// Timer usato per vuotare il pool
        /// </summary>
        private Timer _clearTimer;

        protected bool _disposing = false;

        #endregion

        #region Constructor

        public Pool()
        {
            initName();
            initLogger();
            createTimer();
            TooManyResourcesAcquired += onTooManyResourcesAcquired;
        }

        //~Pool()
        //{
        //    Clear();
        //}

        #endregion

        #region Configuration

        public String Name
        {
            get { return _name; }
            set
            {
                _name = value;
                initLogger();
            }
        }

        public String Id
        {
            get { return _name; }
        }


        public int Size
        {
            get { return _size; }
            set { _size = value; }
        }

        public int ClearTimeout
        {
            get { return _clearTimeout; }
            set
            {
                _clearTimeout = value;
                disposeTimer();
                createTimer();
            }
        }

        #endregion

        #region Properties (Get)

        public int Count
        {
            get { return _pool.Count; }
        }

        public int Available
        {
            get { return Count; }
        }

        public int InUse
        {
            get { return _inUse; }
        }

        #endregion

        #region Events

        public event EventHandler TooManyResourcesAcquired;

        #endregion

        #region Operations

        public virtual T Acquire()
        {
            lock (_pool)
            {
                T resource;
                if (_pool.Count > 0)
                    resource = _pool.Dequeue();
                else
                    resource = Create();

                ++_inUse;

#if MBLOG
                 Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer memoryBuffer = resource as Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer;
                if (memoryBuffer!=null)
                {
                    StackTrace st = new StackTrace(true);
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(String.Format("MemoryBuffer {0} owned by {1} acquired! Now in use {2}", memoryBuffer.Id, memoryBuffer.OwnerId, _inUse));
                    sb.AppendLine("Callstack:");
                    sb.AppendLine(st.ToString());
                    _logger.Info(sb.ToString());
                }
#endif

                if (_size != 0 && _inUse > _size)
                    TooManyResourcesAcquired(this, new EventArgs());

                return resource;
            }
        }

        public virtual void Release(T resource)
        {
            lock (_pool)
            {
                Debug.Assert(!_pool.Contains(resource));
                Debug.Assert(_inUse > 0);

                _pool.Enqueue(resource);

                --_inUse;

                if (_disposing)
                {
                    _logger.InfoFormat("Clearing pool {0} again!", _name);
                    Clear(); 
                }
#if MBLOG
                Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer memoryBuffer = resource as Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer;
                if (memoryBuffer != null)
                {
                    StackTrace st = new StackTrace(true);
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(String.Format("MemoryBuffer {0} owned by {1} released! Now in use {2}. Pool status {3}", memoryBuffer.Id, memoryBuffer.OwnerId, _inUse, _disposing?"disposing":"alive"));
                    sb.AppendLine("Callstack:");
                    sb.AppendLine(st.ToString());
                    _logger.Info(sb.ToString());
                }
#endif
            }
        }

        #endregion

        #region Overridables

        protected virtual T Create()
        {
            return new T();
        }

        protected virtual int Clear()
        {
            lock (_pool)
            {
                int count = _pool.Count;

                if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
                {
                    foreach (IDisposable resource in _pool)
                    {
                        resource.Dispose();
                    }
                }

                _pool.Clear();

                _logger.InfoFormat("Pool {0} cleared {1} resources!", _name, count);

                return count;
            }
        }

        #endregion

        #region IDisposable

        public virtual void Dispose()
        {
            disposeTimer();
            Clear();
            TooManyResourcesAcquired -= onTooManyResourcesAcquired;
        }

        #endregion

        #region Implementation

        private void initName()
        {
            String id = Guid.NewGuid().ToString("D");
            _name = String.Format("{0}.{1}", typeof(T).Name, id);
        }

        private void initLogger()
        {
            String poolType = GetType().FullName;
            int sep = poolType.IndexOf('`');
            if (sep != -1)
                poolType = poolType.Substring(0, sep);

            String loggerName = String.Format("{0}.{1}", poolType, _name);
            _logger = LogManager.GetLogger(loggerName);
        }

        private void clearPool(Object/*Pool*/ self)
        {
            if (Count <= 2)
                return;

            int count = Clear();

            _logger.InfoFormat("Cleared pool {0}: removed {1} items, still {2} items in use", 
                _name, 
                count, 
                _inUse);
        }

        private void createTimer()
        {
            if (_clearTimeout != 0)
                _clearTimer = new Timer(clearPool, this, _clearTimeout, _clearTimeout);
        }

        private void disposeTimer()
        {
            if (_clearTimer != null)
                _clearTimer.Dispose();
        }

        private void onTooManyResourcesAcquired(Object self, EventArgs eventArgs)
        {
            if (_inUse < 2 * _size)
            {
                _logger.WarnFormat("Too many resources acquised: {0} available, {1} in use", 
                    Available, 
                    InUse);
            }
            else if (_inUse < 4 * _size)
            {
                _logger.ErrorFormat("Too many resources acquised: {0} available, {1} in use", 
                    Available, 
                    InUse);
            }
            else
            {
                _logger.FatalFormat("Too many resources acquised: {0} available, {1} in use", 
                    Available, 
                    InUse);
            }
        }

        #endregion

        #region Debug

        #endregion
    }

    /// <summary>
    /// Classe base per i pool limitati
    /// </summary>
    public class FixedPool<T> : Pool<T> where T : class, new()
    {
        #region Private Data

        protected SemaphoreSlim _semaphore;

        protected AutoResetEvent _poolCompleted;

        protected int _waitTimeout = 1000;

        // ricordarsi di rimetterlo a 2000 prima di fare il checkin facendo in mod che si possa impostare da configurazione
        protected int _disposeTimeout = 0;

        #endregion

        #region Constructor

        public FixedPool(int size)
        {
            _size = size;
            //_semaphore = new Semaphore(size, size);
            _semaphore = new SemaphoreSlim(size, size);
        }

        #endregion

        #region Properties

        public new int Available
        {
            get { return Size - InUse; }
        }

        public int WaitTimeout
        {
            set { _waitTimeout = value; }
        }

        public int DisposeTimeout
        {
            set { _disposeTimeout = value; }
        }

        #endregion

        #region Operations

        public override T Acquire()
        {
            if (_disposing)
            {
                throw new ObjectDisposedException(String.Format("Unable to acquire a resource from pool {0} already disposed", _name));
            }

            if (_semaphore.Wait(_waitTimeout))
            {
                return base.Acquire();
            }
            else
            {
                throw new TimeoutException(String.Format("Unable to acquire a resource from pool {0} in {1} ms", _name, _waitTimeout));
            }
        }

        public virtual T Acquire(int timeout)
        {
            if (_disposing)
            {
                throw new ObjectDisposedException(String.Format("Unable to acquire a resource from pool {0} already disposed", _name));
            }

            if (_semaphore.Wait(timeout))
            {
                return base.Acquire();
            }
            else
            {
                return null;
            }
        }

        public override void Release(T resource)
        {
            base.Release(resource);

            _semaphore.Release();

            onPoolCompleted();
        }

        #endregion

        #region IDisposable

        public override void Dispose()
        {

            //lock (_pool)
            //{
            //    if (typeof(Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer).IsAssignableFrom(typeof(T)))
            //    {
            //        StringBuilder sb = new StringBuilder();
            //        sb.AppendLine(String.Format("Before startDisposing call on {0}. In use {1}", Id, _inUse));
            //        foreach (T resource in _pool)
            //        {
            //            Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer memoryBuffer = resource as Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer;
            //            sb.AppendLine(String.Format("MemoryBuffer {0} ReferenceCounter {1}", memoryBuffer.Id, memoryBuffer.ReferenceCounter));
            //        }
            //        _logger.Info(sb.ToString());
            //    }
            //}

            if (startDisposing())
            {
                if (!_poolCompleted.WaitOne(_disposeTimeout))
                {
                    string errorMessage = String.Format("Dispose: timeout error while waiting for {1} resource termination ({0}).", _disposeTimeout, _inUse);
                    _logger.Warn(errorMessage);

                    //// NL
                    //{
                    //    StringBuilder sb = new StringBuilder();
                    //    lock (_pool)
                    //    {
                    //        sb.AppendLine(String.Format("On timeout waiting memorybuffer release on {0}. In use {1}", Id, _inUse));
                    //        foreach (T resource in _pool)
                    //        {
                    //            Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer memoryBuffer = resource as Radiocor.RriGenerics.HiPerfTcp.MemoryBuffer;
                    //            sb.AppendLine(String.Format("MemoryBuffer {0} ReferenceCounter {1}", memoryBuffer.Id, memoryBuffer.ReferenceCounter));
                    //        }
                    //    }

                    //    StackTrace st = new StackTrace(true);
                    //    sb.AppendLine("Callstack:");
                    //    sb.AppendLine(st.ToString());
                    //    _logger.Info(sb.ToString());
                    //}

                    throw new TimeoutException(errorMessage);
                }
            } 

            base.Dispose();
        }

        private bool startDisposing()
        {
            lock (_pool)
            {
                _disposing = true;
                if ((_disposeTimeout > 0) && _inUse > 0)
                {
                    _poolCompleted = new AutoResetEvent(false);
                    _logger.InfoFormat("StartDisposing: {0} resource(s) need(s) to be released before dispose termination.", _inUse);
                }
                return _poolCompleted != null;
            }
        }

        private void onPoolCompleted()
        {
            if (_poolCompleted != null && _inUse == 0)
                _poolCompleted.Set();
        }

        #endregion
    }

}
